from PyQt6.QtCore import QByteArray,Qt,QObject, pyqtSlot, pyqtSignal,pyqtProperty,QTimer
from PyQt6.QtWidgets import QFileDialog
from PyQt6.QtBluetooth import (QBluetoothServiceDiscoveryAgent,QBluetoothUuid,QBluetoothServiceInfo,QBluetoothServer,QBluetoothAddress, QBluetoothDeviceDiscoveryAgent,
                                 QBluetoothDeviceInfo, QBluetoothLocalDevice)

import logging
import os
import traceback
import math
serviceUuid="e8e10f95-1a70-4b27-9ccf-02010264e9c9"

class Bridge(QObject):
    # ballX=pyqtProperty(float,getBallX,None,notiy=ballChanged)
    # ballY=pyqtProperty(float,getBallY,None,notiy=ballChanged)
    # leftBlockY=pyqtProperty(float,getLeftBlockY,None,notiy=leftBlockChanged)
    # rightBlockY=pyqtProperty(float,getRightBlockY,None,notiy=rightBlockChanged)
    # showDialog=pyqtProperty(bool,getShowDialog,None,notiy=showDialogChanged)
    
    # role=pyqtProperty(int,getRole,None,notiy=roleChanged)
    def __init__(self):
        super().__init__()
        self.m_ballX = 0.5
        self.m_ballY = 0.9
        self.m_leftBlockY = 0.4
        self.m_rightBlockY = 0.4
        self.m_speedy = 0.002
        self.m_speedx = 0.002
        self.m_serverInfo=None
        self.socket=None
        self.discoveryAgent=None
        self.interval=5
        self.m_resultLeft=0
        self.m_resultRight=0
        self.m_showDialog=False
        self.m_role=0
        self.m_proportionX=0
        self.m_proportionY=0
        self.m_timer = QTimer(self);
        self.m_timer.timeout.connect(self.update)
        self.m_message=""
        self.m_serviceFound=False
    def startGame(self):
        self.m_resultLeft = self.m_resultRight = 0;
        self.resultChanged.emit();
        self.m_showDialog = False;
        self.showDialogChanged.emit();
        self.m_timer.start(10);

    @pyqtSlot()
    def update(self):
        #QByteArray size;
        # Server is only updating the coordinates
        #! [Updating coordinates]
        if (self.m_role == 1):
            self.checkBoundaries();
            self.m_ballY += self.m_speedy;
            self.m_ballX += self.m_speedx;
            size="%f %f %f \n" %(self.m_ballX,self.m_ballY,self.m_leftBlockY)
            b=QByteArray(size)
            print(b)
            self.socket.write(b);
            self.ballChanged.emit();
        elif (self.m_role == 2):
            size="%f \n" % self.m_rightBlockY
            # self.socket.write(size)
            b=QByteArray(size)
            print(b)
            self.socket.write(b);
    #! [Updating coordinates]
    def updateBall(self,bX,bY):
        self.m_ballX = bX;
        self.m_ballY = bY;
    @pyqtSlot(float)
    def updateLeftBlock(self,lY):
        self.m_leftBlockY = lY;
    @pyqtSlot(float)
    def updateRightBlock(self,rY):
        self.m_rightBlockY = rY;

    def checkBoundaries(self):
        ballWidth = 1.0 / 27;
        blockSize = 1.0 / 27;
        blockHeight = 1.0 / 5;

        ballCenterY = self.m_ballY + ballWidth / 2.;
        #     #! [Checking the boundaries]
        if (((self.m_ballX + ballWidth) > (1.0 - blockSize)) and  (ballCenterY < (self.m_rightBlockY + blockHeight))
            and (ballCenterY > self.m_rightBlockY)):
            # right paddle collision
            # simulating paddle surface to be a quarter of a circle
            paddlecenter = self.m_rightBlockY + blockHeight / 2.;
            relpos = (ballCenterY - paddlecenter) / (blockHeight / 2.); # [-1 : 1]
            surfaceangle = math.pi/4 * relpos;

            # calculation of surface normal
            normalx = -math.cos(surfaceangle);
            normaly = math.sin(surfaceangle);

            # calculation of surface tangent
            tangentx = math.sin(surfaceangle);
            tangenty = math.cos(surfaceangle);

            # calculation of tangentialspeed
            tangentialspeed = tangentx * self.m_speedx + tangenty * self.m_speedy;
            # calculation of normal speed
            normalspeed = normalx * self.m_speedx + normaly * self.m_speedy;

            # speed increase of 10%
            normalspeed *= 1.1;

            if (normalspeed < 0): # if we are coming from the left. To avoid double reflections
                self.m_speedx = tangentialspeed * tangentx - normalspeed * normalx;
                self.m_speedy = tangentialspeed * tangenty - normalspeed * normaly;
        elif ((self.m_ballX < blockSize) and (ballCenterY < (self.m_leftBlockY + blockHeight))
                   and (ballCenterY > self.m_leftBlockY)) :
            # left paddle collision
            # simulating paddle surface to be a quarter of a circle
            paddlecenter = self.m_leftBlockY + blockHeight / 2.;
            relpos = (ballCenterY - paddlecenter) / (blockHeight / 2.); # [-1 : 1]
            surfaceangle = math.pi/4 * relpos;

            # calculation of surface normal
            normalx = math.cos(surfaceangle);
            normaly = math.sin(surfaceangle);

            # calculation of surface tangent
            tangentx = -math.sin(surfaceangle);
            tangenty = math.cos(surfaceangle);

            # calculation of tangentialspeed
            tangentialspeed = tangentx * self.m_speedx + tangenty * self.m_speedy;
            # calculation of normal speed
            normalspeed = normalx * self.m_speedx + normaly * self.m_speedy;

            # speed increase of 10%
            normalspeed *= 1.1;

            if (normalspeed < 0): # if we are coming from the left. To avoid double reflections
                self.m_speedx = tangentialspeed * tangentx - normalspeed * normalx;
                self.m_speedy = tangentialspeed * tangenty - normalspeed * normaly;
        elif (self.m_ballY < 0):
            self.m_speedy = abs(self.m_speedy);
        elif (self.m_ballY + ballWidth > 1.0):
            self.m_speedy = -abs(self.m_speedy);
        #! [Checking the boundaries]
        elif ((self.m_ballX + ballWidth) > 1.0):
            self.m_resultLeft+=1;
            self.m_speedx = -0.002;
            self.m_speedy = -0.002;
            self.m_ballX = 0.5;
            self.m_ballY = 0.9;

            self.checkResult();
            msg="result "
            msg +="%d " % self.m_resultLeft
            msg +="%d \n" % self.m_resultRight
            b=QByteArray(msg)
            print(b)
            self.socket.write(b);
            self.resultChanged.emit();
        elif (self.m_ballX < 0):
            self.m_resultRight+=1;
            self.m_speedx = 0.002;
            self.m_speedy = -0.002;
            self.m_ballX = 0.5;
            self.m_ballY = 0.9;

            self.checkResult();
            msg="result %d %d \n" % (self.m_resultLeft,self.m_resultRight)
            b=QByteArray(msg)
            print(b)
            self.socket.write(b);
            self.resultChanged.emit();

    def checkResult(self):
        if (self.m_resultRight < 10 and self.m_resultLeft < 10):
            return;

        if (self.m_resultRight == 10 and self.m_role == 2):
            self.setMessage("Game over. You win! Next game starts in 10s");
        
        elif (self.m_resultRight == 10 and self.m_role == 1):
            self.setMessage("Game over. You lose! Next game starts in 10s");
        elif (self.m_resultLeft == 10 and self.m_role == 1) :
            self.setMessage("Game over. You win! Next game starts in 10s");
        elif (self.m_resultLeft == 10 and self.m_role == 2):
            self.setMessage("Game over. You lose! Next game starts in 10s");
        self.m_timer.stop();
        QTimer.singleShot(10000, self.startGame)
    @pyqtSlot()
    def startServer(self):
        self.setMessage("Starting the server")

        # make discoverable
        QBluetoothLocalDevice().setHostMode(QBluetoothLocalDevice.HostMode.HostDiscoverable)

        #! [Starting the server]
        self.m_server =QBluetoothServer(QBluetoothServiceInfo.Protocol.RfcommProtocol, self)
        self.m_server.newConnection.connect(self.clientConnected)
        self.m_server.errorOccurred.connect(self.serverError)
        # print("before uuid")
        uuid=QBluetoothUuid(serviceUuid);
        print(uuid,dir(uuid))
        # print("before listen")
        self.m_serverInfo = self.m_server.listen(uuid, "PingPong server")
        print("after listen")
        #! [Starting the server]
        self.setMessage("Server started, waiting for the client. You are the left player.")
        # m_role is set to 1 if it is a server
        self.m_role = 1;
        self.roleChanged.emit();
    @pyqtSlot()
    def startClient(self):
        #! [Searching for the service]
        self.addr=QBluetoothAddress()
        print(self.addr)
        self.discoveryAgent = QBluetoothServiceDiscoveryAgent(self.addr)
        self.discoveryAgent.serviceDiscovered.connect(self.addService)
        self.discoveryAgent.finished.connect(self.done)
        self.discoveryAgent.errorOccurred.connect(self.serviceScanError)
        self.uuid=QBluetoothUuid(serviceUuid)
        self.discoveryAgent.setUuidFilter(self.uuid)
        self.discoveryAgent.start(QBluetoothServiceDiscoveryAgent.DiscoveryMode.FullDiscovery)

        #! [Searching for the service]
        self.setMessage("Starting server discovery. You are the right player")
        # m_role is set to 2 if it is a client
        self.m_role = 2;
        self.roleChanged.emit();

    def clientConnected(self):
        #! [Initiating server socket]
        if (not self.m_server.hasPendingConnections()):
            self.setMessage("FAIL: expected pending server connection");
            return;
        self.socket = self.m_server.nextPendingConnection()
        if (not self.socket):
            return;
        self.socket.setParent(self);
        self.socket.readyRead.connect(self.readSocket)
        self.socket.disconnected.connect(self.clientDisconnected)
        self.socket.errorOccurred.connect(self.socketError)

        #! [Initiating server socket]
        self.setMessage("Client connected. Get ready!")
        QTimer.singleShot(3000, self.startGame)

    def clientDisconnected(self):
        self.setMessage("Client disconnected")
        self.m_timer.stop();
    def socketError(self,error):
        print(error)
        self.m_timer.stop();

    def serverError(self,error):
        print("serverError====")
        print(error)
        self.m_timer.stop()
    def done(self):

        print("Service scan done")
        if (not self.m_serviceFound):
            self.setMessage("PingPong service not found");

    def addService(self,service):
        if (self.m_serviceFound):
            return;
        self.m_serviceFound = True

        self.setMessage("Service found. Stopping discovery and creating connection...");
        self.discoveryAgent.stop();
        #! [Connecting the socket]
        self.socket = QBluetoothSocket(QBluetoothServiceInfo.RfcommProtocol);
        self.socket.connectToService(service);

        self.socket.readyRead.connect(self.readSocket);
        self.socket.connected.connect(self.serverConnected);
        self.socket.disconnected.connect(self.serverDisconnected);
        #! [Connecting the socket]
    def serviceScanError(self, error):
        self.setMessage("Scanning error"+str(error))

    def serverConnected(self):
        self.setMessage("Server Connected. Get ready!");
        QTimer.singleShot(3000, self.startGame)

    def serverDisconnected(self):
        self.setMessage("Server Disconnected");
        self.m_timer.stop();
    def readSocket(self):
        if (not self.socket):
            return;
        sep = ' ';
        line=None
        while (self.socket.canReadLine()):
            line = self.socket.readLine();
            # print(line)
            if "result" in line:
                result = line.split(sep)
                if (len(result) > 2):
                    leftSide = result[1]
                    rightSide = result[2]
                    self.m_resultLeft = leftSide.toInt();
                    self.m_resultRight = rightSide.toInt();
                    self.resultChanged.emit();
                    self.checkResult();
        if (self.m_role == 1):
            boardSize =line.split(sep);
            if (len(boardSize) > 1):
                rightBlockY = boardSize[0]
                self.m_rightBlockY = float(rightBlockY)
                self.rightBlockChanged.emit()
        elif (self.m_role == 2):
            boardSize = line.split(sep);
            if (len(boardSize) > 2):
                ballX = boardSize[0]
                ballY = boardSize[1]
                leftBlockY = boardSize[2]
                self.m_ballX = float(ballX)
                self.m_ballY = float(ballY)
                self.m_leftBlockY = float(leftBlockY)
                self.leftBlockChanged.emit();
                self.ballChanged.emit();
    def setMessage(self,message):
        self.m_showDialog = True;
        print(message)
        self.m_message = message;
        self.showDialogChanged.emit();
        # pass
    def get_message(self):
        return self.m_message
    def get_role(self):
        return self.m_role
    def get_leftResult(self):
        return self.m_resultLeft
    def get_rightResult(self):
        return self.m_resultRight
    def get_ballX(self):
        return self.m_ballX
    def get_ballY(self):
        return self.m_ballY
    def get_leftBlockY(self):
        return self.m_leftBlockY
    def get_rightBlockY(self):
        return self.m_rightBlockY
    def get_showDialog(self):
        return self.m_showDialog;        
    ballChanged=pyqtSignal();
    leftBlockChanged=pyqtSignal();
    rightBlockChanged=pyqtSignal();
    showDialogChanged=pyqtSignal();
    roleChanged=pyqtSignal();
    resultChanged=pyqtSignal();        
    # Q_pyqtProperty(float ballX READ ballX NOTIFY ballChanged)
    ballX=pyqtProperty(float,get_ballX,None,notify=ballChanged)
    # Q_pyqtProperty(float ballY READ ballY NOTIFY ballChanged)
    ballY=pyqtProperty(float,get_ballY,None,notify=ballChanged)
    # Q_pyqtProperty(float leftBlockY READ leftBlockY NOTIFY leftBlockChanged)
    leftBlockY=pyqtProperty(float,get_leftBlockY ,None,notify=leftBlockChanged)
    # Q_pyqtProperty(float rightBlockY READ rightBlockY NOTIFY rightBlockChanged)
    rightBlockY=pyqtProperty(float,get_rightBlockY ,None,notify=rightBlockChanged)
    # Q_pyqtProperty(bool showDialog READ showDialog NOTIFY showDialogChanged)
    showDialog=pyqtProperty(bool,get_showDialog,None,notify=showDialogChanged)
    # Q_pyqtProperty(QString message READ message NOTIFY showDialogChanged)
    message=pyqtProperty(str,get_message,None,notify=showDialogChanged)
    # Q_pyqtProperty(int role READ role NOTIFY roleChanged)
    role=pyqtProperty(int,get_role,None,notify=roleChanged)
    # Q_pyqtProperty(int leftResult READ leftResult NOTIFY resultChanged)
    leftResult=pyqtProperty(int,get_leftResult,None,notify=resultChanged)
    rightResult=pyqtProperty(int,get_rightResult,None,notify=resultChanged)
    # Q_pyqtProperty(int rightResult READ rightResult NOTIFY resultChanged)
    